<script lang="ts">
  import { T, useTask } from '@threlte/core'
  import { Edges, PositionalAudio, useAudioListener, useCursor, useGltf } from '@threlte/extras'
  import { Spring, Tween } from 'svelte/motion'
  import {
    CylinderGeometry,
    DoubleSide,
    Mesh,
    MeshStandardMaterial,
    PositionalAudio as ThreePositionalAudio,
    MathUtils
  } from 'three'
  import Button from './Button.svelte'
  import Disc from './Disc.svelte'
  import type { TurntableProps } from './types'

  let { isPlaying = $bindable(false), volume = $bindable(0), ...rest }: TurntableProps = $props()

  let discSpeed = new Tween(0, {
    duration: 1e3
  })

  let armPos = new Spring(0)

  let started = $state(false)

  export const toggle = async () => {
    if (!started) {
      await context.resume()
      started = true
    }
    if (isPlaying) {
      discSpeed.set(0)
      armPos.set(0)
      isPlaying = false
    } else {
      discSpeed.set(1)
      armPos.set(1)
      isPlaying = true
    }
  }

  let audio = $state.raw<ThreePositionalAudio>()

  const { context } = useAudioListener()
  const analyser = context.createAnalyser()
  $effect(() => {
    if (audio) audio.getOutput().connect(analyser)
  })
  const pcmData = new Float32Array(analyser.fftSize)
  useTask(() => {
    if (!audio) return
    analyser.getFloatTimeDomainData(pcmData)
    let sumSquares = 0.0
    for (const amplitude of pcmData) {
      sumSquares += amplitude * amplitude
    }
    volume = Math.sqrt(sumSquares / pcmData.length)
  })

  let sideA = '/audio/side_a.mp3'
  let sideB = '/audio/side_b.mp3'
  let source = $state(sideA)
  const changeSide = () => {
    source = source === sideA ? sideB : sideA
  }

  let coverOpen = $state(false)
  const coverAngle = new Spring(0)
  $effect(() => {
    if (coverOpen) coverAngle.set(80)
    else coverAngle.set(0)
  })

  const { onPointerEnter, onPointerLeave } = useCursor()

  const gltf = useGltf<{
    nodes: {
      Cover: Mesh
    }
    materials: {}
  }>('/models/turntable/cover.glb')

  const coverGeometry = $derived($gltf?.nodes.Cover.geometry)
</script>

<T.Group {...rest}>
  <!-- DISC -->
  <Disc
    position.x={0.5}
    position.y={1.01}
    discSpeed={discSpeed.current}
  />

  <!-- CASE -->
  <T.Mesh
    receiveShadow
    castShadow
    position.y={0.5}
  >
    <T.BoxGeometry args={[6, 1, 4.4]} />
    <T.MeshStandardMaterial color="#eedbcb" />
    <Edges
      scale={1.001}
      color="black"
    />
  </T.Mesh>

  <!-- COVER -->
  <T.Group
    position.y={1}
    position.z={-2.2}
    rotation.x={-coverAngle.current * MathUtils.DEG2RAD}
  >
    {#if coverGeometry}
      <T.Mesh
        geometry={coverGeometry}
        scale={[3, 0.5, 2.2]}
        position.y={0.5}
        position.z={2.2}
        onclick={() => (coverOpen = !coverOpen)}
        onpointerenter={onPointerEnter}
        onpointerleave={onPointerLeave}
      >
        <T.MeshStandardMaterial
          color="#ffffff"
          roughness={0.08}
          metalness={0.8}
          envMapIntensity={1}
          side={DoubleSide}
          transparent
          opacity={0.65}
        />
        <Edges color="white" />
      </T.Mesh>
    {/if}
  </T.Group>

  <!-- SIDE BUTTON -->
  <Button
    position={[-2.3, 1.01, 0.8]}
    onClick={changeSide}
    text={source === sideA ? 'SIDE B' : 'SIDE A'}
  />

  <!-- PLAY/PAUSE BUTTON -->
  <Button
    position={[-2.3, 1.01, 1.7]}
    onClick={toggle}
    text={isPlaying ? 'PAUSE' : 'PLAY'}
  />

  <!-- ARM -->
  <T.Group
    position={[2.5, 1.55, -1.8]}
    rotation.z={MathUtils.DEG2RAD * 90}
    rotation.y={MathUtils.DEG2RAD * 90 - armPos.current * 0.3}
  >
    <T.Mesh
      castShadow
      material={new MeshStandardMaterial({
        color: 0xffffff
      })}
      geometry={new CylinderGeometry(0.1, 0.1, 3, 12)}
      position.y={1.5}
    >
      <T.CylinderGeometry args={[0.1, 0.1, 3, 12]} />
      <T.MeshStandardMaterial color="#ffffff" />
      <Edges
        color="black"
        thresholdAngle={80}
      />
    </T.Mesh>
  </T.Group>

  {#if started}
    <PositionalAudio
      autoplay
      bind:ref={audio}
      refDistance={15}
      loop
      playbackRate={discSpeed.current}
      src={source}
      directionalCone={{
        coneInnerAngle: 90,
        coneOuterAngle: 220,
        coneOuterGain: 0.3
      }}
    />
  {/if}
</T.Group>
